﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using DeepZoomPivotConstructor.ErrorAndLoggingInterfaces;
using DeepZoomPivotConstructor.VisualLib;
using DeepZoomPivotConstructor.VisualLib.Hotspots;
using GSRender;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using RenderEngine;
using RenderEngineInterfaces;

namespace t_GSRender
{
    /// <summary>
    /// Summary description for UnitTest1
    /// </summary>
    [TestClass]
    public class t_RenderUsingGS
    {
        public t_RenderUsingGS()
        {
        }

        private TestContext testContextInstance;

        /// <summary>
        ///Gets or sets the test context which provides
        ///information about and functionality for the current test run.
        ///</summary>
        public TestContext TestContext
        {
            get
            {
                return testContextInstance;
            }
            set
            {
                testContextInstance = value;
            }
        }

        [TestInitialize]
        public void InitForTest()
        {
            t_TestingLibrary.Utilities.MyClassInit();
            t_TestingLibrary.Utilities.AddPart(new RenderControl());
            t_TestingLibrary.Utilities.AddPart(new LayoutHelper());
            t_TestingLibrary.Utilities.AddPart(new SimpleFileRendering.JPEGFileRender());
            DPI = 150;
        }

        /// <summary>
        /// What is the rendering dpi we should be sending on down?
        /// </summary>
        [Export("RenderingDPI", typeof(int))]
        private int DPI { get; set; }

        [TestCleanup]
        public void ShutDownTest()
        {
            t_TestingLibrary.Utilities.MyClassDone();
        }

        [TestMethod]
        public void TestCreation()
        {
            RenderUsingGS gs = new RenderUsingGS();
            gs = null;
        }

        [TestMethod]
        [DeploymentItem("normalPSprinting.ps")]
        public void TestWillRender()
        {
            FileInfo orf = new FileInfo("normalPSprinting.ps");
            FileInfo f = new FileInfo("TestWillRender.ps");
            orf.CopyTo(f.FullName, true);
            f.Refresh();
            Assert.IsTrue(f.Exists, "File for testing does not exist!");

            RenderUsingGS gs = new RenderUsingGS();

            Assert.AreEqual(RenderEngineInterfaces.RenderPluginPriority.RPPGeneric,
                gs.RenderPriority(f));
            f.Delete();
            f.Refresh();

            Assert.IsFalse(f.Exists, "File for testing shouldn't exist!");
            Assert.AreEqual(RenderEngineInterfaces.RenderPluginPriority.RPPCantDoIt,
                gs.RenderPriority(f));
        }

#if false
        [TestMethod]
        public void RenderSinglePSTalk2()
        {
            /// This is a 12 meg file found at: http://indico.cern.ch/materialDisplay.py?contribId=s1t14&materialId=0&confId=a042880
            /// We currently don't have the ability to tell that this talk is oriented "correctly" -- there is nothing in it that tells us.
            /// However, Adobe seems to get it right. See the bug on codeplex.
            /// So we expect this test to fail.

            RenderSingleTalk(new FileInfo("normalPSprinting2.ps"), 46, 8.5, 11.0);
        }
#endif

        [TestMethod]
        [DeploymentItem("normalPSprinting.ps")]
        public void TestRenderAPSFile()
        {
            RenderSingleTalk(new FileInfo("normalPSprinting.ps"), 6, 8.5, 11.0);
        }

        [TestMethod]
        [DeploymentItem("regularPDFfile.pdf")]
        public void RenderSimplePDF()
        {
            RenderSingleTalk(new FileInfo("regularPDFfile.pdf"), 6, 8.5, 11.0);
        }

        /// <summary>
        /// In at least one version of gs view, this pdf caused an infinite loop... we shouldn't hang b/c of that, so
        /// test that here.
        /// </summary>
        [TestMethod]
        [DeploymentItem("infinitePDF.pdf")]
        [Timeout(4 * 60 * 1000)]
        public void RenderHangingPDF()
        {
            // Expected pages == -1 - we don't know how many of them it will have time to do! :-)
            RenderSingleTalk(new FileInfo("infinitePDF.pdf"), -1, 8.5, 11.0, timer: 30 * 1000);

            // Look to see if the oldest file is about 2 minutes old
            var now = DateTime.Now;
            var d = new DirectoryInfo("RenderHangingPDF");
            var fs = from f in d.EnumerateFiles("*.png")
                     orderby f.LastWriteTime descending
                     select f;
            var fnewest = fs.Take(2).ToArray();
            Assert.AreEqual(2, fnewest.Length, "expected at leaset two files");
            var delta = fnewest[0].LastWriteTime - fnewest[1].LastWriteTime;
            Assert.IsTrue(delta.TotalSeconds < 32, "expected the time difference between the two oldest files to be about 30 seconds");

            var procs = Process.GetProcessesByName("gsview");
            Assert.AreEqual(0, procs.Length, "some gsview processes are still running");
        }

        [TestMethod]
        [DeploymentItem("missingPlots.pdf")]
        public void RenderMissingPlots()
        {
            /// You have to look yourself - look at the second page to see if you can see the front page of a paper...
            RenderSingleTalk(new FileInfo("missingPlots.pdf"), 40, 8.3333333333333339, 11.1133333333333);
        }

        [TestMethod]
        [DeploymentItem("truncatedPDF.pdf")]
        public void TestTruncatedPDF()
        {
            RenderSingleTalk(new FileInfo("truncatedPDF.pdf"), 27, 10.94, 14.22);
        }

        [Export(typeof(IRenderItem))]
        [RenderInfo(typeof(FileInfo))]
        class RenderAndThenCrash : IRenderItem
        {
            private int count = 0;

            public System.Threading.Tasks.Task<Visual> Render(object what)
            {
                return System.Threading.Tasks.Task<Visual>.Factory.StartNew(() =>
                    {
                        count++;
                        if (count > 3)
                        {
                            throw new InvalidOperationException("Failed to do this render!");
                        }
                        return new VisualBitmapImage(1.0, 1.0, 100, Color.Black);
                    });
            }

            public RenderPluginPriority RenderPriority(object what)
            {
                FileInfo fi = what as FileInfo;
                if (fi == null)
                    return RenderPluginPriority.RPPCantDoIt;

                if (fi.FullName.EndsWith(".png"))
                {
                    return RenderPluginPriority.RPPSpecificlyJustThisOne;
                }

                return RenderPluginPriority.RPPCantDoIt;
            }
        }

        [TestMethod]
        [DeploymentItem("regularPDFfile.pdf")]
        public void TestRenderWithBadJPG()
        {
            FileInfo f = GetPrivateTestName(new FileInfo("regularPDFfile.pdf"));

            RenderUsingGS gs = new RenderUsingGS();
            gs.UseTalkDirForTemp = true;
            t_TestingLibrary.Utilities.AddPart(new MyLogger());
            t_TestingLibrary.Utilities.AddPart(new RenderAndThenCrash());
            t_TestingLibrary.Utilities.Compose(gs);
            Visual file = gs.Render(f).Result;

            Assert.AreEqual(3, file.SGNodeList.Count(), "Wrong number of slides came back!");
        }

        [TestMethod]
        [DeploymentItem("singlePage.pdf")]
        public void RenderSinglePagePDF()
        {
            /// There should be no title page on this guy - so we should get only
            /// a single image. The "0" for # of pages below is because RenderSingleTalk always
            /// adds one for the title page!

            RenderSingleTalk(new FileInfo("singlePage.pdf"), 0, 8.5, 11.0);
        }

        [TestMethod]
        [DeploymentItem("singlePage.pdf")]
        public void TestHSCenteringOnePage()
        {
            var visualForFile = RenderSingleTalk(new FileInfo("singlePage.pdf"), 0, 8.5, 11.0);
            var allHS = from v in visualForFile.GetHierarchicalEnumerator(ignore => false)
                        where v.GetType() == typeof(VisualHotSpot)
                        select v;
            Assert.AreEqual(1, allHS.Count(), "Should be one centering hot spot");
            Assert.AreEqual(1, CountHotspotActions(allHS.First() as VisualHotSpot, typeof(HSActionCenterHere)), "Should be only a single centering hot spot!");

            var otherHS = from v in visualForFile.GetHierarchicalEnumerator(ignore => false)
                          where v.GetType() == typeof(VisualHotSpot)
                          select v;
            Assert.AreEqual(0, CountHotspotActions(otherHS.First() as VisualHotSpot, typeof(HSActionCenterThere)), "Expected zero arrow hot spots on a file with single page");
        }

        /// <summary>
        /// Count the number of hot spots of a given type assoicated with a VisualHotSpot
        /// </summary>
        /// <param name="visual"></param>
        /// <param name="hsType"></param>
        /// <returns></returns>
        private int CountHotspotActions(VisualHotSpot visual, Type hsType)
        {
            int count = 0;
            visual.GetAllActoinData(hs =>
                {
                    if (hs.GetType() == hsType)
                    {
                        count += 1;
                    }
                });
            return count;
        }

        [TestMethod]
        [DeploymentItem("regularPDFfile.pdf")]
        public void TestHSCenteringSixPage()
        {
            var visualForFile = RenderSingleTalk(new FileInfo("regularPDFfile.pdf"), 6, 8.5, 11.0);
            var allHS = from v in visualForFile.GetHierarchicalEnumerator(ignore => false)
                        where v.GetType() == typeof(VisualHotSpot)
                        select v as VisualHotSpot;
            Assert.AreEqual(7, CountHotspotActions(allHS, typeof(HSActionCenterHere)), "Should be seven hot spots (6 pages + title)");

            Assert.AreEqual(19, CountHotspotActions(allHS, typeof(HSActionCenterThere)), "Expected 5 move to next page guys, prev, and up, plus one down!");
        }

        /// <summary>
        /// Tallies up the actions for this particular guy in all HS's.
        /// </summary>
        /// <param name="allHS"></param>
        /// <param name="typeOfAction"></param>
        /// <returns></returns>
        private int CountHotspotActions(IEnumerable<VisualHotSpot> allHS, Type typeOfAction)
        {
            return allHS.Sum(vhs => CountHotspotActions(vhs, typeOfAction));
        }

        [TestMethod]
        [DeploymentItem("singlePage.pdf")]
        public void TestDefaultDPISetting()
        {
            CheckDPIOfRendering(150);
        }

        [TestMethod]
        [DeploymentItem("singlePage.pdf")]
        public void TestDPISetting()
        {
            DPI = 300;
            t_TestingLibrary.Utilities.AddPart(this);
            CheckDPIOfRendering(300);
        }

        private void CheckDPIOfRendering(int defaultSetting)
        {
            FileInfo fold = new FileInfo("singlePage.pdf");
            FileInfo f = GetPrivateTestName(fold);

            RenderUsingGS gs = new RenderUsingGS();
            t_TestingLibrary.Utilities.AddPart(new MyLogger());
            t_TestingLibrary.Utilities.Compose(gs);
            gs.UseTalkDirForTemp = true;
            Visual file = gs.Render(f).Result;

            Assert.AreEqual(1, file.SGNodeList.Count, "Wrong number of slides for post-script talk!");

            var firstPage = file.SGNodeList[0];
            FileInfo img = new FileInfo(firstPage.FileName);
            Assert.IsTrue(img.Exists, "Can't find the output page image");

            var sizes = DeepZoomPivotConstructor.Utilities.Image.GetImageSize(img);
            Assert.IsTrue(Math.Abs(defaultSetting * 11 - sizes[1]) < 2, "The width in pixels incorrect");
            Assert.IsTrue(Math.Abs((int)(defaultSetting * 8.5) - sizes[0]) < 2, "The height in pixels incorrect");
        }

        [TestMethod]
        [DeploymentItem("singlePage.pdf")]
        public void TestNoTempDroppings()
        {
            FileInfo fold = new FileInfo("singlePage.pdf");
            FileInfo fl = GetPrivateTestName(fold);

            RenderUsingGS gs = new RenderUsingGS();
            t_TestingLibrary.Utilities.AddPart(new MyLogger());
            t_TestingLibrary.Utilities.Compose(gs);
            Visual file = gs.Render(fl).Result;

            Assert.AreEqual(1, file.SGNodeList.Count, "Wrong number of slides for post-script talk!");

            var images = from f in fl.Directory.GetFiles()
                         where f.Extension == ".jpg"
                         select f;
            Assert.AreEqual(0, images.Count(), "Expected only the PDF file in the sub directory!");
        }

        class Params
        {
            [Export("RenderingTempDir")]
            public DirectoryInfo RenderTempDir;
        }

        [Export(typeof(IErrorLogger))]
        class MyLogger : IErrorLogger
        {
            public void LogMessage(string ID, string message)
            {
                Trace.WriteLine(ID + " - " + message);
            }

            public void LogMessage(string ID, string message, Exception e)
            {
                Trace.WriteLine(ID + " - " + message + " - " + e.Message + " - " + e.StackTrace);
            }

            public void SetLogfileLocation(DirectoryInfo newDirectory)
            {
                throw new NotImplementedException();
            }
        }


        [TestMethod]
        [DeploymentItem("singlePage.pdf")]
        public void TestRenderToTempDir()
        {
            FileInfo fold = new FileInfo("singlePage.pdf");
            FileInfo fl = GetPrivateTestName(fold);

            Params np = new Params();
            np.RenderTempDir = new DirectoryInfo(fl.DirectoryName + Path.DirectorySeparatorChar + "render");
            if (np.RenderTempDir.Exists)
            {
                np.RenderTempDir.Delete(true);
            }
            np.RenderTempDir.Create();
            t_TestingLibrary.Utilities.AddPart(np);
            t_TestingLibrary.Utilities.AddPart(new MyLogger());

            RenderUsingGS gs = new RenderUsingGS();
            t_TestingLibrary.Utilities.Compose(gs);
            Visual file = gs.Render(fl).Result;

            Assert.AreEqual(1, file.SGNodeList.Count, "Wrong number of slides for post-script talk!");
            Assert.AreEqual(1, np.RenderTempDir.GetDirectories()[0].EnumerateFiles().Where(f => f.Extension == ".png").Count(), "Expected one rendered file in the temp sub dir");
        }

        [TestMethod]
        [DeploymentItem("singlePage.pdf")]
        public void TestRenderTalkWithSpace()
        {
            ///
            /// Make sure ghostscrip tisn't have trouble with spaces!
            /// 

            FileInfo fold = new FileInfo("singlePage.pdf");
            Assert.IsTrue(fold.Exists, "File for testing doesn't exist!");

            DirectoryInfo d = new DirectoryInfo("bogus with space");
            d.Create();
            FileInfo f = new FileInfo(d.FullName + "\\pdf file with space.pdf");
            fold.CopyTo(f.FullName);

            t_TestingLibrary.Utilities.AddPart(new MyLogger());
            RenderUsingGS gs = new RenderUsingGS();
            t_TestingLibrary.Utilities.Compose(gs);
            gs.UseTalkDirForTemp = true;
            Visual file = gs.Render(f).Result;

            /// The +1 is for the title page
            Assert.AreEqual(1, file.SGNodeList.Count, "Wrong number of slides for post-script talk!");

            Assert.IsNotNull(file.Info, "Expecting basic info about this file!");
            Assert.AreEqual(f.Name.Substring(0, f.Name.Length - f.Extension.Length), file.Info.Title, "Title of master visual is not right!");
        }

#if false
        [TestMethod]
        public void RenderPDFWithWhiteSpace()
        {
            /// We are expecting this to fail...
            Visual v = RenderSingleTalk(new FileInfo("..\\..\\..\\t_GSRender\\PDFWithWhiteSpace.pdf"), 9, 8.5, 11.0);
        }
#endif

        [TestMethod]
        [DeploymentItem("rotatedpsfile.ps")]
        public void RenderPSThatNeedsRotating()
        {
            RenderSingleTalk(new FileInfo("rotatedpsfile.ps"), 22, 8.5, 11.0);
        }

        [Import]
        private IRenderEngine MyRender { get; set; }

        [TestMethod]
        [DeploymentItem("regularPDFfile.pdf")]
        public void TestMetadata()
        {
            t_TestingLibrary.Utilities.AddPart(new RenderUsingGS() { UseTalkDirForTemp = true });
            t_TestingLibrary.Utilities.AddPart(new MyLogger());
            t_TestingLibrary.Utilities.Compose(this);

            FileInfo f = GetPrivateTestName(new FileInfo("regularPDFfile.pdf"));

            Visual v = MyRender.Render(f);
            Assert.IsNotNull(v, "Expected a rendering to come back from that pdf!!!");
            Assert.AreEqual(7, v.SGNodeList.Count, "Expected 6 images to come back - didn't see them!");
        }

        /// <summary>
        /// Run a rendering for a sinle talk.
        /// </summary>
        /// <param name="fold"></param>
        /// <param name="expectedPages"></param>
        private Visual RenderSingleTalk(FileInfo fold, int expectedPages, double expectedHeight, double expectedWidthPerPage, int timer = 2 * 60 * 1000)
        {
            Assert.IsTrue(fold.Exists, "File for testing doesn't exist!");

            FileInfo f = GetPrivateTestName(fold);

            RenderUsingGS gs = new RenderUsingGS() { TimeToLetGSRun = timer };
            gs.UseTalkDirForTemp = true;
            t_TestingLibrary.Utilities.AddPart(new MyLogger());
            t_TestingLibrary.Utilities.Compose(gs);
            Visual file = gs.Render(f).Result;

            /// The +1 is for the title page
            if (expectedPages >= 0)
            {
                Assert.AreEqual(expectedPages + 1, file.SGNodeList.Count, "Wrong number of slides for post-script talk!");

                if (expectedPages > 0)
                {
                    var firstPage = file.SGNodeList[0];

                    if (expectedHeight > 0)
                    {
                        MakeSureEqual(expectedHeight, firstPage.Height, "Height of image isn't correct");
                    }

                    if (expectedWidthPerPage > 0)
                    {
                        MakeSureEqual(expectedWidthPerPage, firstPage.Width, "Width of pages didn't come back correctly");
                    }
                }
            }

            Assert.IsNotNull(file.Info, "Expecting basic info about this file!");
            Assert.AreEqual(f.Name.Substring(0, f.Name.Length - f.Extension.Length), file.Info.Title, "Title of master visual is not right!");
            return file;
        }

        /// <summary>
        /// Check for equality...
        /// </summary>
        /// <param name="expectedHeight"></param>
        /// <param name="p"></param>
        /// <param name="p_2"></param>
        private void MakeSureEqual(double expected, double actual, string message)
        {
            double delta = Math.Abs(expected - actual);
            double sig = delta / 0.02;
            Assert.IsTrue(sig < 2.0, "message - expected " + expected + " and actual " + actual);
        }

        /// <summary>
        /// Generate a fileinfo where we can copy this test's filename - that way we can be
        /// sure two tests, even if they start from the same file, don't overwrite each others
        /// files.
        /// </summary>
        /// <param name="startingName"></param>
        /// <returns></returns>
        private FileInfo GetPrivateTestName(FileInfo startingName)
        {
            string name = startingName.Name.Substring(0, startingName.Name.Length - startingName.Extension.Length);
            DirectoryInfo newdir = new DirectoryInfo(TestContext.TestName);
            newdir.Create();

            name = name + "-" + TestContext.TestName;
            FileInfo f = new FileInfo(newdir.FullName + "\\" + name + startingName.Extension);
            startingName.CopyTo(f.FullName);
            return f;
        }

        /// <summary>
        /// Here is a eps file with a bounding box. Do we get it right!?
        /// </summary>
        [TestMethod]
        [DeploymentItem("Energy_EMB_AllStat.eps")]
        public void TestEPSRender()
        {
            Visual v = RenderSingleTalk(new FileInfo("Energy_EMB_AllStat.eps"), 0, 3.0, 5.0);

            /// Make sure the aspect ratio is right...

            Trace.WriteLine("The width is " + v.Width + ", and the height is " + v.Height);
            Assert.IsTrue(v.Width > v.Height, "The width isn't larger than the height!");

            /// Next, make sure the size is about right!

            TestClose(7.8733, v.Width, "Width is not right");
            TestClose(5.3266, v.Height, "Height isn't right");
        }

        /// <summary>
        /// Here is a eps file with a bounding box. Do we get it right!?
        /// </summary>
        [TestMethod]
        [DeploymentItem("Energy_EMB_AllStat.ps")]
        public void TestEPSInPSRender()
        {
            Visual v = RenderSingleTalk(new FileInfo("Energy_EMB_AllStat.ps"), 0, 3.0, 5.0);

            /// Make sure the aspect ratio is right...

            Trace.WriteLine("The width is " + v.Width + ", and the height is " + v.Height);
            Assert.IsTrue(v.Width > v.Height, "The width isn't larger than the height!");

            /// Next, make sure the size is about right!

            TestClose(7.8733, v.Width, "Width is not right");
            TestClose(5.3266, v.Height, "Height isn't right");
        }

        /// <summary>
        /// Check that these two numbers are close. Fail if not.
        /// </summary>
        /// <param name="p"></param>
        /// <param name="p_2"></param>
        /// <param name="p_3"></param>
        private void TestClose(double expected, double actual, string failmessage)
        {
            double percent = Math.Abs((expected - actual) / expected);
            Assert.IsTrue(percent < 0.02, "Expected: " + expected + "; Actual: " + actual + " - " + failmessage);
        }

        [TestMethod]
        [DeploymentItem("badPSRender.ps")]
        public void TestBadPSFile()
        {
            FileInfo f = GetPrivateTestName(new FileInfo("badPSRender.ps"));

            RenderUsingGS gs = new RenderUsingGS();
            gs.UseTalkDirForTemp = true;
            t_TestingLibrary.Utilities.AddPart(new MyLogger());
            t_TestingLibrary.Utilities.Compose(gs);
            Visual file = gs.Render(f).Result;

            Assert.IsNull(file);
        }
    }
}
